package jge.datatypes.math;

/**
 * A Class describing a 3-dimensional vector with X, Y, Z and W components
 * @author Sam
 * @version 1.0
 */
public final class JGEVector {
	
	private float x, y, z, w;
	
	/**
	 * Default Constructor<br>
	 * Creates a zero vector
	 */
	public JGEVector() {
		this.x = 0;
		this.y = 0;
		this.z = 0;
		this.w = 0;
	}
	
	/**
	 * Parameterized Constructor<br>
	 * Creates a vector with the coordinates specified
	 * @param x - the x-axis coordinate
	 * @param y - the y-axis coordinate
	 * @param z - the z-axis coordinate
	 */
	public JGEVector(float x, float y, float z) {
		this.x = x;
		this.y = y;
		this.z = z;
		this.w = 1.0f;
	}
	
	/**
	 * Sets the coordinates of the vector
	 * @param x - the new x-axis coordinate
	 * @param y - the new y-axis coordinate
	 * @param z - the new z-axis coordinate
	 */
	public void set(float x, float y, float z) {
		this.x = x;
		this.y = y;
		this.z = z;
		this.w = 1.0f;
	}
	
	/**
	 * Sets the coordinates and the 'w' value of the vector
	 * @param x - the new x-axis coordinate
	 * @param y - the new y-axis coordinate
	 * @param z - the new z-axis coordinate
	 * @param w - the w value
	 */
	public void set(float x, float y, float z, float w) {
		this.set(x, y, z);
		this.w = w;
	}
	
	/**
	 * Set the x-axis coordinate of the vector
	 * @param x - the new x-axis coordinate
	 */
	public void setX(float x) {
		this.x = x;
	}
	
	/**
	 * Set the y-axis coordinate of the vector
	 * @param y - the new z-axis coordinate
	 */
	public void setY(float y) {
		this.y = y;
	}
	
	/**
	 * Set the z-axis coordinate of the vector
	 * @param z - the new z-axis coordinate
	 */
	public void setZ(float z) {
		this.z = z;
	}
	
	/**
	 * Set the 'w' value of the vector
	 * @param x - the new w value
	 */
	public void setW(float w) {
		this.w = w;
	}
	
	/**
	 * Returns the magnitude of the vector. <br>
	 * Uses {@link Math.sqrt()} so may be slow. If length is expected to be 1 use {@link getSqrLength()}.
	 * @return the vectors magnitude
	 */
	public float getLength() {
		return (float) Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2) + Math.pow(this.z, 2));
	}
	
	/**
	 * Returns the squared value of the vectors magnitude. <br>
	 * Faster operation than {@link getLength()} so is ideal for basic comparisons between vector lengths
	 * or when expected length is 1.
	 * @return the square of the vectors magnitude
	 */
	public float getSqrLength() {
		return (float) (Math.pow(this.x, 2) + Math.pow(this.y, 2) + Math.pow(this.z, 2));
	}

	/**
	 * Negates the vector that the instance method is called on
	 */
	public void negate() {
		this.x = -this.x;
		this.y = -this.y;
		this.z = -this.z;
	}
	
	/**
	 * Divides the vectors components by its length, resulting in a "normalized" vector with a length of 1
	 */
	public void normalize() {
		float length = this.getLength();
		this.x = this.x / length;
		this.y = this.y / length;
		this.z = this.z / length;
	}
	
	/**
	 * Returns the angle between this vector and the provided vector v
	 * @param v - the second vector
	 * @return the angle between this vector and v 
	 */
	public float angleWith(JGEVector v) {
		return (float) Math.acos(this.multiply(v) / (this.getLength() * v.getLength()));
	}
	
	/**
	 * Sets this vector to the difference of two other vectors (v - u)
	 * @param u - first vector
	 * @param v - second vector
	 */
	public void Difference(JGEVector u, JGEVector v) {
		this.x = v.x - u.x;
		this.y = v.y - u.y;
		this.z = v.z - u.z;
		this.w = 1.0f;
	}
	
	/**
	 * Adds vector v to the the vector that the instance method is called on
	 * @param v - the vector to add
	 * @return the resultant vector
	 */
	public JGEVector add(JGEVector v) {
		return new JGEVector(this.x + v.x, this.y + v.y, this.z + v.z);
	}
	
	/**
	 * Subtracts vector v from the vector that the instance method is called on
	 * @param v - the vector to subtract
	 * @return the resultant vector
	 */
	public JGEVector subtract(JGEVector v) {
		return new JGEVector(this.x - v.x, this.y - v.y, this.z - v.z);
	}
	
	/**
	 * Performs a scalar multiplication on the vector that the instance method is called on
	 * @param scalar - the scalar to multiply the vector by
	 * @return the resultant vector
	 */
	public JGEVector multiply(float scalar) {
		return new JGEVector(this.x * scalar, this.y * scalar, this.z * scalar);
	}
	
	/**
	 * Performs a dot product vector multiplication on the vector that the instance method is called on
	 * @param v - the vector to multiply the instance vector by
	 * @return the dot product instance * v
	 */
	public float multiply(JGEVector v) {
		return this.x * v.x + this.y * v.y + this.z * v.z;
	}
	
	/**
	 * Multiplies this vector by a matrix and returns a resultant vector.
	 * @param m - the matrix to multiply by
	 * @return the resultant vector
	 */
	public JGEVector multiply(JGEMatrix m) {
		JGEVector resultant = new JGEVector();
		
		float resultX = x * m.get(1, 1) + y * m.get(2, 1) + z * m.get(3, 1) + m.get(4, 1);
		float resultY = x * m.get(1, 2) + y * m.get(2, 2) + z * m.get(3, 2) + m.get(4, 2);
		float resultZ = x * m.get(1, 3) + y * m.get(2, 3) + z * m.get(3, 3) + m.get(4, 3);
		float resultW = x * m.get(1, 4) + y * m.get(2, 4) + z * m.get(3, 4) + m.get(4, 4);
		
		resultant.set(resultX / resultW, resultY / resultW, resultZ / resultW, 1.0f);
		
		return resultant;
	}
	
	/**
	 * Performs a scalar division on the vector that the instance method is called on
	 * @param scalar - the scalar to divide the vector by
	 * @return the resultant vector
	 */
	public JGEVector divide(float scalar) {
		return new JGEVector(this.x / scalar, this.y / scalar, this.z / scalar);
	}
	
	/**
	 * Performs a cross product between this vector and a provided vector
	 * @param v - the provided vector
	 * @return the resultant vector
	 */
	public JGEVector cross(JGEVector v) {
		return new JGEVector(this.y * v.z - this.z * v.y,
							  this.z * v.x - this.x * v.z,
							  this.x * v.y - this.y * v.z);
	}

	/**
	 * @return the x-axis coordinate of this vector
	 */
	public float getX() {
		return x;
	}

	/**
	 * @return the y-axis coordinate of this vector
	 */
	public float getY() {
		return y;
	}

	/**
	 * @return the z-axis coordinate of this vector
	 */
	public float getZ() {
		return z;
	}

	/**
	 * @return the w value of this vector
	 */
	public float getW() {
		return w;
	}
}
